import Taro from "@tarojs/taro"
import { wxLogin, isLoginRequest, setIsAuthing, getIsAuthing, isLogin } from "@/utils/auth"
import { getStorageToken, setStorageToken } from '@/utils/ls/token'
import { DataExtact, ResultSchema } from "@/apis/model"
import { useAuthStore } from "@/store"

interface IReqConfig {
  baseUrl: string
}

type IFetchOpts<R, B extends string | TaroGeneral.IAnyObject | ArrayBuffer = any> = Taro.request.Option<R, B> & {
  /** @default true */
  showLoading?: boolean
  loadingText?: string
  urlCustom?: boolean
}

export type ICapableFetchOpts<R, B extends string | TaroGeneral.IAnyObject | ArrayBuffer = any> = Omit<IFetchOpts<R, B>, "url" | "method" | "data">

interface IWaitingTask {
  reqOpts: IFetchOpts<ResultSchema>
  resolve: IFn
  reject: IFn
}

const maxTryCount = 2
const tryRequestMap = new Map<string, number>()
const waitingTasks: IWaitingTask[] = []

function runWaitingTasks(fulfilled: boolean) {
  setIsAuthing(false)
  !fulfilled && terminateTasks()
  while (waitingTasks.length) {
    const next = waitingTasks.shift() as IWaitingTask
    request({
      ...next.reqOpts,
      success: next.resolve,
      fail: next.reject
    })
  }
}

function terminateTasks() {
  waitingTasks.length = 0
  tryRequestMap.clear()
}

export function getReqConfig(): IReqConfig {
  return {
    baseUrl: process.env.TARO_APP_API
  }
}

export function request<R extends ResultSchema = any, B extends string | TaroGeneral.IAnyObject | ArrayBuffer = any>(
  opts: IFetchOpts<R, B>
) {
  return new Promise<DataExtact<R>>((resolve, reject) => {
    if (getIsAuthing() && !isLoginRequest(opts.url)) {
      waitingTasks.push({
        reject,
        resolve,
        reqOpts: opts
      })
      return
    }
    const { url, method = "GET", data, showLoading = true, loadingText, header = {}, success, fail, complete } = opts
    const token = getStorageToken()
    const baseHeader: TaroGeneral.IAnyObject = {
      "X-Requested-With": "XMLHttpRequest"
    }
    if (token) {
      baseHeader['Authorization'] = "Bearer " + token
    }
    if (showLoading) {
      Taro.showLoading({
        mask: true,
        title: loadingText ?? "加载中"
      })
    }
    return Taro.request<R, B>({
      url: opts.urlCustom === true ? url : getReqConfig().baseUrl + url,
      method,
      data,
      header: {
        ...baseHeader,
        ...header
      },
      success: function (res) {
        console.log("req success", res, opts)
        if (showLoading) {
          Taro.hideLoading()
        }
        if (res.statusCode !== 200) {
          if (res.statusCode === 401) {
            if (isLogin()) useAuthStore.getState().clearAuth()
            Taro.showToast({
              title: "请重新登录",
              icon: "none",
              mask: false
            })

            const tryCount = tryRequestMap.get(opts.url)
            if (typeof tryCount === "undefined") {
              tryRequestMap.set(opts.url, 1)
            } else {
              if (tryCount >= maxTryCount) {
                Taro.showToast({
                  title: "请求失败，请稍候再试",
                  icon: "none",
                  mask: false
                })
                reject(res)
                tryRequestMap.delete(opts.url)
                return
              } else {
                tryRequestMap.set(opts.url, tryCount + 1)
              }
            }

            waitingTasks.push({
              reject,
              resolve,
              reqOpts: opts
            })
            if (!getIsAuthing()) {
              setIsAuthing(true)
              wxLogin(runWaitingTasks)
            }
          } else {
            Taro.showToast({
              title: "请求失败，请稍候再试",
              icon: "none",
              mask: false
            })
            reject(res)
          }
          return
        }
        receiveToken(res)
        if (tryRequestMap.has(opts.url)) {
          tryRequestMap.delete(opts.url)
        }
        if (!requestIsFullfilled(res)) {
          if (res.data.message) {
            Taro.showToast({
              title: res.data.message || "请求失败，请稍候再试",
              icon: "none",
              mask: false
            })
          }
          return reject(res)
        }
        success && success(res.data.data)
        resolve(res.data.data)
      },
      fail: function (res) {
        console.log("req fail", res, opts)
        if (showLoading) {
          Taro.hideLoading()
        }

        Taro.showToast({
          title: res.errMsg || "请求失败，请稍候再试",
          icon: "none",
          mask: false
        })

        fail && fail(res)
        reject(res)
      },
      complete: function (res) {
        complete && complete(res)
      }
    })
  })
}
/**
 * send a Get request
 * @param url request url
 * @param query query params
 * @param opts request opts
 * @returns RequestTask
 */
export const httpGet = function <R extends ResultSchema<any> = any, B extends string | TaroGeneral.IAnyObject | ArrayBuffer = any>(
  url: string,
  query: B = {} as B,
  opts: ICapableFetchOpts<R, B> = {},
) {
  return request<R>({ url, method: "GET", data: query, ...opts })
}

/**
 * send a Post request
 * @param url request url
 * @param data post data
 * @param opts request opts
 * @returns RequestTask
 */
export const httpPost = function <R extends ResultSchema<any> = any, B extends string | TaroGeneral.IAnyObject | ArrayBuffer = any>(
  url: string,
  data: B = {} as B,
  opts: ICapableFetchOpts<R, B> = {},
) {
  return request<R>({ url, method: "POST", data, ...opts })
}

/**
 * send a Put request
 * @param url request url
 * @param data Put data
 * @param opts request opts
 * @returns RequestTask
 */
export const httpPut = function <R extends ResultSchema<any> = any, B extends string | TaroGeneral.IAnyObject | ArrayBuffer = any>(
  url: string,
  data: B = {} as B,
  opts: ICapableFetchOpts<R, B> = {},
) {
  return request<R>({ url, method: "PUT", data, ...opts })
}

/**
 * send a Delete request
 * @param url request url
 * @param data Delete data
 * @param opts request opts
 * @returns RequestTask
 */
export const httpDelete = function <R extends ResultSchema<any> = any, B extends string | TaroGeneral.IAnyObject | ArrayBuffer = any>(
  url: string,
  data: B = {} as B,
  opts: ICapableFetchOpts<R, B> = {},
) {
  return request<R>({ url, method: "DELETE", data, ...opts })
}

export type IUploadFileOptions = {
  /**
   * @default 'file'
   */
  name?: string
  formData?: Taro.uploadFile.Option["formData"]
  /**
   * @default true
   */
  withCredentials?: boolean
  /**
   * @default false
   */
  urlCustom?: boolean
}

let uploadCount = 0
const defaultOptions: IUploadFileOptions = {
  urlCustom: false,
  withCredentials: true,
  formData: {},
  name: 'file'
}
/** 文件上传*/
export const uploadFile = function (
  url: string,
  filePath: string,
  options: IUploadFileOptions = {}
) {
  const { formData, ...normalOptions } = options
  options = Object.assign({}, defaultOptions, normalOptions)
  Taro.showLoading({
    title: "上传中"
  })
  uploadCount++
  return Taro.uploadFile({
    url: options.urlCustom === true ? url : getReqConfig().baseUrl + url,
    filePath,
    name: options.name!,
    header: {
      Authorization: options.withCredentials ? "Bearer " + getStorageToken() : ""
    },
    formData: Object.assign({}, defaultOptions.formData, formData),
    complete: function () {
      uploadCount--
      if (uploadCount === 0) {
        Taro.hideLoading()
      }
    }
  })
}

export function requestIsFullfilled(
  res: Taro.request.SuccessCallbackResult<ResultSchema>
): boolean {
  return res.data && res.data.code === 0
}

function receiveToken(res: Taro.request.SuccessCallbackResult<ResultSchema>) {
  const token = res.header['access-token'] || res.header['Access-Token']
  if (token) setStorageToken(token)
}

